package secondary_classes;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;

import secondary_classes.OrderBook.AmendType;



/**
 * @author Rohit
 *
 * @param instrument Security
 * @param date Date of the order
 * @param time Time of the order
 * @param recordType Order status i.e. Enter, Amend, Delete
 * @param price The price 
 * @param volume The volume
 * @param value The value
 * @param transId Transaction ID
 * @param bidId Bid ID
 * @param askId Ask ID
 * @param bidOrAsk "A" = Ask, "B" = Bid
 */
public class Order {
	private static final int INSTRUMENT_CODE_LENGTH = 3;
	
	private static final String DATE_FORMAT = "dd-MMM-yy";
	private static final String DATE_FORMAT_ALT = "yyyyMMdd";
	private static final String TIME_FORMAT1 = "HH:mm:ss";
	private static final String TIME_FORMAT2 = "HH:mm:ss.SS";
	private static final String TIME_FORMAT3 = "HH:mm:ss.SSS";
	
	private static final DateFormat dateFormat = new SimpleDateFormat(DATE_FORMAT);
	private static final DateFormat dateFormatAlt = new SimpleDateFormat(DATE_FORMAT_ALT);
	private static final DateFormat timeFormat1 = new SimpleDateFormat(TIME_FORMAT1);
	private static final DateFormat timeFormat2 = new SimpleDateFormat(TIME_FORMAT2);
	private static final DateFormat timeFormat3 = new SimpleDateFormat(TIME_FORMAT3);
	
	private DateFormat usedTimeFormat;
	private DateFormat usedEntryTimeFormat;
	
	private String instrument;
	private Date date;
	private Date time;
	private String recordType;
	private BigDecimal price;
	private BigInteger volume;
	private BigDecimal undisclosedVolume;
	private BigDecimal value;
	private String qualifiers;
	private BigInteger transId;
	private BigInteger bidId;
	private BigInteger askId;
	private String bidOrAsk;
	private Date entryTime;
	private BigDecimal oldPrice;
	private BigInteger oldVolume;
	
	private String string;
	
	/**
	 * @throws Exception 
	 */
	public Order(String[] values) throws Exception {
		
		if (values.length < ReadOrders.requiredFields.length) {
			throw new Exception("Missing required fields in input orders file");
		}
		
		String instrumentString = "";
		String dateString = "";
		String timeString = "";
		String recordTypeString = "";
		String priceString = "";
		String volumeString = "";
		String valueString = "";
		String transIdString = "";
		String bidIdString = "", askIdString = "", bidOrAskString = "";
		String undisclosedVolumeString = "";
		String qualifiersString = "";
		String entryTimeString = "";
		String oldPriceString = "";
		String oldVolumeString = "";
		
		// instrument
		instrument = values[0];
		if (instrument.length() != INSTRUMENT_CODE_LENGTH) {
			throw new Exception("Instrument's code is not of length " + INSTRUMENT_CODE_LENGTH);
		}
		instrumentString = instrument;
		
		// date
		// TODO not sure if there is an upper limit on this - lower limit checked by parser automatically
		if (!values[1].equals("")) {
			try {
				date = dateFormat.parse(values[1]);
			} catch (ParseException e) {
				try {
					date = dateFormatAlt.parse(values[1]);
				} catch (ParseException e1) {
					throw new Exception("Incorrect date format");
				}
			}
			dateString = dateFormat.format(date);
		}
		
		
		// time
		if (!values[2].equals("")) {
			try {
				time = timeFormat3.parse(values[2]);
				usedTimeFormat = timeFormat3;
			} catch (ParseException e) {
				try {
					time = timeFormat2.parse(values[2]);
					usedTimeFormat = timeFormat2;
				} catch (ParseException e1) {
					try {
						time = timeFormat1.parse(values[2]);
					} catch (ParseException e2) {
						throw new Exception("Incorrect time format");
					}
					usedTimeFormat = timeFormat1;
				}
			}
			timeString = usedTimeFormat.format(time);
		}
		
		// recordType
		recordType = values[3];
		if (!(recordType.equals("ENTER") || recordType.equals("DELETE") || recordType.equals("AMEND") ||
				recordType.equals("TRADE") || recordType.equals("OFFTR") || recordType.equals("CANCEL_TRADE"))) {
			throw new Exception("Record type is not valid (it must be either ENTER, DELETE, AMEND, TRADE, OFFTR or CANCEL_TRADE)");
		}
		recordTypeString = recordType;
		
		// price
		if (!values[4].equals("")) {
			try {
				price = new BigDecimal(values[4]);
			} catch (NumberFormatException e) {
				throw new Exception("Invalid price");
			}
			priceString = price.toPlainString();
		}
		
		// volume
		if (!values[5].equals("")) {
			try {
				volume = new BigInteger(values[5]);
			} catch (NumberFormatException e) {
				throw new Exception("Invalid volume");
			}
			volumeString = volume.toString();
		}
		
		// value
		if (!recordType.equals("DELETE")) {
			try {
				value = new BigDecimal(values[6]);
			} catch (NumberFormatException e) {
				throw new Exception("Invalid value");
			}
			valueString = value.toPlainString();
		}
		
			// check price, volume and value
		if (!recordType.equals("DELETE")) {
			// Price
			if (price.compareTo(BigDecimal.ZERO) <= 0) {
				throw new Exception("Price is less than or equal to zero");
			}
			// Volume
			if (volume.compareTo(BigInteger.ZERO) <= 0) {
				throw new Exception("Volume is less than or equal to zero");
			}
			// Value
			BigDecimal expected = price.multiply(new BigDecimal(volume));
			if (value.compareTo(expected) != 0) {
				throw new Exception("Value is not equal to Price * Volume (" + 
				price.toPlainString() + " * " + volume + " = " + 
				expected.toPlainString() + " != " + value.toPlainString() + ")");
			}
		}
		
		// transId
		if (!values[7].equals("")) {
			try {
				transId = new BigInteger(values[7]);
			} catch (NumberFormatException e) {
				throw new Exception("Invalid transaction ID");
			}
			transIdString = transId.toString();
		}
			// check
		if ((recordType.equals("ENTER") || recordType.equals("DELETE") || recordType.equals("AMEND")) && transId.compareTo(BigInteger.ZERO) < 0) {
				throw new Exception("Transaction ID cannot be negative");
		}
		
		// bidOrAsk, bidId, askId
		bidOrAsk = values[10];
		if (bidOrAsk.equals("B")) {
			try {
				bidId = new BigInteger(values[8]);
			} catch (NumberFormatException e) {
				throw new Exception("Invalid bid ID");
			}
			bidIdString = bidId.toString();
			askIdString = "";
		} else if (bidOrAsk.equals("A")) {
			try {
				askId = new BigInteger(values[9]);
			} catch (NumberFormatException e) {
				throw new Exception("Invalid ask ID");
			}
			bidIdString = "";
			askIdString = askId.toString();
		} else if (bidOrAsk.equals("") && (recordType.equals("TRADE") || recordType.equals("OFFTR"))) {
			bidId = new BigInteger(values[8]);
			askId = new BigInteger(values[9]);
			bidIdString = bidId.toString();
			askIdString = askId.toString();
		} else if (recordType.equals("CANCEL_TRADE")) {
			bidId = null;
			askId = null;
			bidIdString = "";
			askIdString = "";
		} else {
			throw new Exception("Not specified as a bid, ask or trade");
		}
		bidOrAskString = bidOrAsk;
		
		if (values.length > 11) {
			// undisclosedVolume
			if (!values[11].equals("")) {
				try {
					undisclosedVolume = new BigDecimal(values[11]);
				} catch (NumberFormatException e) {
					throw new Exception("Invalid undisclosed volume");
				}
				undisclosedVolumeString = undisclosedVolume.toPlainString();
			}
			
			if (values.length > 12) {
				// qualifiers
					// TODO check qualifiers are valid
				qualifiers = values[12];
				qualifiersString = qualifiers;
				
				if (values.length > 13) {
					// entryTime
					if (!values[13].equals("")) {
						try {
							entryTime = timeFormat2.parse(values[13]);
							usedTimeFormat = timeFormat2;
						} catch (ParseException e) {
							try {
								entryTime = timeFormat1.parse(values[13]);
							} catch (ParseException e1) {
								throw new Exception("Incorrect entry time format");
							}
							usedEntryTimeFormat = timeFormat1;
						}
						entryTimeString = usedEntryTimeFormat.format(entryTime);
					}
					
					if (values.length > 14) {
						// oldPrice
						if (!values[14].equals("")) {
							try {
								oldPrice = new BigDecimal(values[14]);
							} catch (NumberFormatException e) {
								throw new Exception("Invalid old price");
							}
							oldPriceString = oldPrice.toPlainString();
						}
						
						if (values.length > 15) {
							// oldVolume
							
							if (!values[15].equals("")) {
								try {
									oldVolume = new BigInteger(values[15]);
								} catch (NumberFormatException e) {
									throw new Exception("Invalid old volume");
								}
								oldVolumeString = oldVolume.toString();
							}
						}
					}
				}
			}
		}
		
		this.string = instrumentString + "," + dateString + "," + timeString + 
			"," + recordTypeString + "," + priceString + "," + volumeString + "," + 
			undisclosedVolumeString + "," + valueString + "," + qualifiersString + 
			"," + transIdString + "," + bidIdString + "," + askIdString + "," + 
			bidOrAskString + "," + entryTimeString + "," + oldPriceString + "," + 
			oldVolumeString;
		
	}
	
	/**
	 * Only for use in {@link OrderBook#findOrder(BigInteger)}. Do not use anywhere else!
	 * @param orderId bid or ask ID
	 * @param bidOrAsk either "B" or "A" depending on whether to set orderId as bidId or askId
	 */
	public Order(BigInteger orderId, String bidOrAsk) {
		
		if (bidOrAsk.equals("B")) {
			this.bidId = orderId;
		} else if (bidOrAsk.equals("A")) {
			this.askId = orderId;
		}

	}
	
	public String toString() {
		return this.string;
	}
	
	/**
	 * Determines whether an order can be deleted using a DELETE order
	 * @param o The DELETE order
	 * @return true if the given order can be deleted using the given DELETE order,
	 * otherwise false
	 */
	public boolean canBeDeleted(Order o) {
		boolean result = true;
		
		// for an order to be deleted, only the Order ID needs to be known
		if (!(o.getOrderId().equals(getOrderId()))) {
			result = false;
		}
		
		return result;
	}
	
	/**
	 * Determines whether an order can be amended
	 * @param o Order to test
	 * @return true if the given order can be amended with o, otherwise false
	 */
	public boolean canBeAmendedBy(Order o) {
		boolean result = true;
		
		// check for equality, ignoring the volume and price (they can be amended)
		if (!(o.getOrderId().equals(getOrderId()) && o.instrument.equals(instrument)) || (o == null)) {
			result = false;
		}
		
		return result;
	}
	
	/**
	 * Determines the type of amend to make
	 * @return AmendType if it is a valid amend, otherwise null
	 * @throws Exception 
	 */
	public AmendType getAmendType(Order o) throws Exception {
		AmendType result = null;
		if (canBeAmendedBy(o)) {
			if (o.getVolume().compareTo(getVolume()) < 0) {
				result = AmendType.DEC_VOL;
			} else if (o.getVolume().compareTo(getVolume()) > 0) {
				result = AmendType.INC_VOL;
			} else if (o.getPrice().compareTo(getPrice()) != 0) {
				result = AmendType.PRICE;
			}
		} else {
			throw new Exception("Cannot work out type of amend to make");
		}
		return result;
	}
	
	@Override
	public boolean equals(Object o) {
		boolean result = true;
		Order r = (Order) o;
		
		// check for equality
		if (!(r.getOrderId().equals(getOrderId()) && r.date.equals(date) &&
				r.instrument.equals(instrument) && r.price.equals(price) &&
				r.time.equals(time) && r.transId.equals(transId) &&
				r.value.equals(value) && r.volume.equals(volume) &&
				r.recordType.equals((recordType)))) {
			result = false;
		}
		
		return result;
	}
	
	/**
	 * @return the Security
	 */
	public String getInstrument() {
		return instrument;
	}

	/**
	 * @return the date
	 */
	public Date getDate() {
		return date;
	}

	/**
	 * @return the time
	 */
	public Date getTime() {
		return time;
	}

	/**
	 * @return the Order Type
	 */
	public String getRecordType() {
		return recordType;
	}

	/**
	 * @return the price
	 */
	public BigDecimal getPrice() {
		return price;
	}

	/**
	 * @return the volume
	 */
	public BigInteger getVolume() {
		return volume;
	}

	/**
	 * @return the value
	 */
	public BigDecimal getValue() {
		return value;
	}

	/**
	 * @return the Transaction ID
	 */
	public BigInteger getTransId() {
		return transId;
	}

	/**
	 * @return the Bid ID
	 */
	public BigInteger getBidId() {
		return bidId;
	}

	/**
	 * @return the Ask ID
	 */
	public BigInteger getAskId() {
		return askId;
	}

	/**
	 * @return "A" if ask, "B" if bid, "" if trade
	 */
	public String getBidOrAsk() {
		return bidOrAsk;
	}
	
	/**
	 * @return the Order ID
	 */
	public BigInteger getOrderId() {
		if (bidOrAsk.equals("B")) {
			return bidId;
		} else if (bidOrAsk.equals("A")) {
			return askId;
		} else {
			return new BigInteger("-1");
		}
	}

	/**
	 * @return the undisclosedVolume
	 */
	public BigDecimal getUndisclosedVolume() {
		return undisclosedVolume;
	}

	/**
	 * @return the qualifiers
	 */
	public String getQualifiers() {
		return qualifiers;
	}

	/**
	 * @return the entryTime
	 */
	public Date getEntryTime() {
		return entryTime;
	}

	/**
	 * @return the oldPrice
	 */
	public BigDecimal getOldPrice() {
		return oldPrice;
	}

	/**
	 * @return the oldVolume
	 */
	public BigInteger getOldVolume() {
		return oldVolume;
	}

	public boolean exactMatch(Order order) {
		if (order != null) {
			// basics
			if (order.getInstrument().equals(getInstrument()) &&
					order.getPrice().equals(getPrice()) &&
					order.getValue().equals(getValue()) && 
					order.getVolume().equals(getVolume()) &&
					order.getBidId().equals(getBidId()) &&
					order.getAskId().equals(getAskId())) {
				// transaction ID
				if (order.getTransId() == null) {
					if (getTransId() != null) {
						return false;
					}
				} else {
					if (getTransId() == null) {
						return false;
					} else if (!order.getTransId().equals(getTransId())) {
						return false;
					}
				}
				// undisclosed volume
				if (order.getUndisclosedVolume() == null) {
					if (getUndisclosedVolume() != null) {
						return false;
					}
				} else {
					if (getUndisclosedVolume() == null) {
						return false;
					} else if (!order.getUndisclosedVolume().equals(getUndisclosedVolume())) {
						return false;
					}
				}
				// qualifiers
				if (order.getQualifiers() == null) {
					if (getQualifiers() != null) {
						return false;
					}
				} else {
					if (getQualifiers() == null) {
						return false;
					} else if (!order.getQualifiers().equals(getQualifiers())) {
						return false;
					}
				}
				// old price
				if (order.getOldPrice() == null) {
					if (getOldPrice() != null) {
						return false;
					}
				} else {
					if (getOldPrice() == null) {
						return false;
					} else if (!order.getOldPrice().equals(getOldPrice())) {
						return false;
					}
				}
				// old volume
				if (order.getOldVolume() == null) {
					if (getOldVolume() != null) {
						return false;
					}
				} else {
					if (getOldVolume() == null) {
						return false;
					} else if (!order.getOldVolume().equals(getOldVolume())) {
						return false;
					}
				}
				// entry time
				if (order.getEntryTime() == null) {
					if (getEntryTime() != null) {
						return false;
					}
				} else {
					if (getEntryTime() == null) {
						return false;
					} else if (!order.getEntryTime().equals(getEntryTime())) {
						return false;
					}
				}
			} else {
				return false;
			}
			return true;
		} else {
			return false;
		}
	}
	
}
